﻿/**
<code>
  <author name="Zealic" email="zealic(at)gmail.com" type="Creator"/>
</code>
**/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Data;

namespace Zealib.Utilities
{

    /// <summary>
    /// CSV(Comma separated values) file m_Reader.
    /// </summary>
    public class CsvReader : IDisposable
    {
        private readonly Stream m_Stream;
        private readonly StreamReader m_Reader;

        /// <summary>
        /// Create a new m_Reader for the given stream.
        /// </summary>
        /// <param name="s">The m_Stream to read the CSV from.</param>
        public CsvReader(Stream s) : this(s, null) { }

        /// <summary>
        /// Create a new m_Reader for the given stream and encoding.
        /// </summary>
        /// <param name="stream">The stream to read the CSV from.</param>
        /// <param name="encoding">The encoding used.</param>
        public CsvReader(Stream stream, Encoding encoding)
        {
            m_Stream = stream;
            if (!stream.CanRead)
            {
                throw new IOException("Could not read the given CSV stream!");
            }
            m_Reader = (encoding != null) ? new StreamReader(stream, encoding) : new StreamReader(stream);
        }

        public CsvReader(string filename) : this(filename, Encoding.Default) { }

        public CsvReader(string filename, Encoding encoding)
            : this(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), encoding) { }


        public static DataTable ReadToTable(CsvReader reader)
        {
            DataTable result = new DataTable();
            string[] line;
            while ((line = reader.ReadRow()) != null)
            {
                while (result.Columns.Count < line.Length)
                    result.Columns.Add();
                result.Rows.Add(Array.ConvertAll(line, (s) => s.ToString()));
            }
            return result;
        }

        public string[] ReadRow()
        {
            var result = new List<string>();
            string rowData = m_Reader.ReadLine();
            if(rowData == null) return null;
            int pos = -1;
            while (pos < rowData.Length)
            {
                string field = ReadField(ref rowData, ref pos);
                if (field == null) break;
                result.Add(field);
            }

            return result.ToArray();
        }

        private string ReadField(ref string data, ref int pos)
        {
            if (pos == data.Length - 1)
            {
                return null;
            }

            int startIndex = pos + 1;

            // Determine if this is a quoted field
            if (data[startIndex] == '"')
            {
                if (startIndex == data.Length - 1)
                {
                    return "\"";
                }

                int quotePos = 0;
                do
                {
                    if (quotePos == data.Length)
                        data += ("\r\n" + m_Reader.ReadLine());
                    quotePos = FindQuote(data, startIndex + 1);
                } while (quotePos == data.Length);
                pos = quotePos + 1;
                return data.Substring(startIndex + 1, quotePos - startIndex - 1).Replace("\"\"", "\"");
            }

            // The field ends in the next comma or EOL
            int nextComma = data.IndexOf(',', startIndex);
            if (nextComma == -1)
            {
                pos = data.Length;
                return data.Substring(startIndex);
            }
            pos = nextComma;
            return data.Substring(startIndex, nextComma - startIndex);
        }

        private static int FindQuote(string data, int start)
        {
            int i = start - 1;
            while (++i < data.Length)
                if (data[i] == '"')
                {
                    if (i < data.Length - 1 && data[i + 1] == '"')
                    {
                        i++;
                        continue;
                    }
                    return i;
                }
            return i;
        }

        public void Dispose()
        {
            if (m_Reader != null) 
                m_Reader.Close();
            else if (m_Stream != null)
                m_Stream.Close();
        }
    }

}
